Bug 56070 – Can't click button after setting it sensitive.
authorCody Russell <bratsche@gnome.org>
Fri, 1 Aug 2008 03:30:50 +0000 (03:30 +0000)
committerCody Russell <bratsche@src.gnome.org>
Fri, 1 Aug 2008 03:30:50 +0000 (03:30 +0000)
2008-07-31  Cody Russell  <bratsche@gnome.org>

        Bug 56070 – Can't click button after setting it sensitive.

        * gtk/gtkwidget.[ch]
        * gtk/gtkwindow.c
        * gtk/gtkmain.c
        * gtk/gtkbutton.c
        * gtk/gtkprivate.h
        * gdk/gdkevents.h: Synthesize crossing events events where necessary.

        * gtk/tests/crossingevents.c: Add unit tests for crossing events.

        Big thanks to Ed Catmur, Matthias Clasen, and everyone else who
        has worked on and helped out with this.

svn path=/trunk/; revision=20924

ChangeLog
docs/reference/gdk/tmpl/event_structs.sgml
gdk/gdkevents.h
gtk/gtkbutton.c
gtk/gtkmain.c
gtk/gtkprivate.h
gtk/gtkwidget.c
gtk/gtkwidget.h
gtk/gtkwindow.c
gtk/tests/Makefile.am
gtk/tests/crossingevents.c [new file with mode: 0644]

index 7172cd9f74ef1dcea095f13e6df9faf34c09003d..250e739cc6bf3a3c6cfbf5112b81d873e69a9a54 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,19 @@
+2008-07-31  Cody Russell  <bratsche@gnome.org>
+
+       Bug 56070 – Can't click button after setting it sensitive.
+
+       * gtk/gtkwidget.[ch]
+       * gtk/gtkwindow.c
+       * gtk/gtkmain.c
+       * gtk/gtkbutton.c
+       * gtk/gtkprivate.h
+       * gdk/gdkevents.h: Synthesize crossing events events where necessary.
+
+       * gtk/tests/crossingevents.c: Add unit tests for crossing events.
+
+       Big thanks to Ed Catmur, Matthias Clasen, and everyone else who
+       has worked on and helped out with this.
+
 2008-07-31  Matthias Clasen  <mclasen@redhat.com>
 
        Bug 424207 – printing hangs on unreachable cups server
index 58494f5c1d44dc94df487f28f75d50c6b377ee77..8e9b65628fb0a624089cd558c84a0572c9f0f4f6 100644 (file)
@@ -259,8 +259,11 @@ Generated when the pointer enters or leaves a window.
 @y: the y coordinate of the pointer relative to the window.
 @x_root: the x coordinate of the pointer relative to the root of the screen.
 @y_root: the y coordinate of the pointer relative to the root of the screen.
-@mode: the crossing mode (%GDK_CROSSING_NORMAL, %GDK_CROSSING_GRAB or
-  %GDK_CROSSING_UNGRAB).
+@mode: the crossing mode (%GDK_CROSSING_NORMAL, %GDK_CROSSING_GRAB, 
+  %GDK_CROSSING_UNGRAB, %GDK_CROSSING_GTK_GRAB, %GDK_CROSSING_GTK_UNGRAB or
+  %GDK_CROSSING_STATE_CHANGED).  %GDK_CROSSING_GTK_GRAB, %GDK_CROSSING_GTK_UNGRAB,
+  and %GDK_CROSSING_STATE_CHANGED were added in 2.14 and are always synthesized,
+  never native.
 @detail: the kind of crossing that happened (%GDK_NOTIFY_INFERIOR,
   %GDK_NOTIFY_ANCESTOR, %GDK_NOTIFY_VIRTUAL, %GDK_NOTIFY_NONLINEAR or
   %GDK_NOTIFY_NONLINEAR_VIRTUAL).
@@ -474,6 +477,10 @@ Specifies the crossing mode for #GdkEventCrossing.
 @GDK_CROSSING_NORMAL: crossing because of pointer motion.
 @GDK_CROSSING_GRAB: crossing because a grab is activated.
 @GDK_CROSSING_UNGRAB: crossing because a grab is deactivated.
+@GDK_CROSSING_GTK_GRAB: crossing because a GTK+ grab is activated.
+@GDK_CROSSING_GTK_UNGRAB: crossing because a GTK+ grab is deactivated.
+@GDK_CROSSING_STATE_CHANGED: crossing because a GTK+ widget changed state (e.g.
+   sensitivity).
 
 <!-- ##### ENUM GdkNotifyType ##### -->
 <para>
index 87cc83734069256e6877fa956f9bc6d695236f89..e64acb7b9f7f2a631d9d18476a2df364a99d75ad 100644 (file)
@@ -225,7 +225,10 @@ typedef enum
 {
   GDK_CROSSING_NORMAL,
   GDK_CROSSING_GRAB,
-  GDK_CROSSING_UNGRAB
+  GDK_CROSSING_UNGRAB,
+  GDK_CROSSING_GTK_GRAB,
+  GDK_CROSSING_GTK_UNGRAB,
+  GDK_CROSSING_STATE_CHANGED
 } GdkCrossingMode;
 
 typedef enum
index 606b33d70aff97787794c66e1c1b89c770b2ecec..58a6926125b38d94bce857b09b198d03b8a7b550 100644 (file)
@@ -1457,7 +1457,8 @@ gtk_button_leave_notify (GtkWidget        *widget,
   event_widget = gtk_get_event_widget ((GdkEvent*) event);
 
   if ((event_widget == widget) &&
-      (event->detail != GDK_NOTIFY_INFERIOR))
+      (event->detail != GDK_NOTIFY_INFERIOR) &&
+      (GTK_WIDGET_SENSITIVE (event_widget)))
     {
       button->in_button = FALSE;
       gtk_button_leave (button);
index 584265645d09b4ed8776a32e6035e0d784903d91..30328cd1c3db78632129112bff2399293e27acdb 100644 (file)
@@ -1569,25 +1569,15 @@ gtk_main_do_event (GdkEvent *event)
       break;
       
     case GDK_ENTER_NOTIFY:
+      GTK_PRIVATE_SET_FLAG (event_widget, GTK_HAS_POINTER);
+      _gtk_widget_set_pointer_window (event_widget, event->any.window);
       if (GTK_WIDGET_IS_SENSITIVE (grab_widget))
-       {
-         g_object_ref (event_widget);
-         
-         gtk_widget_event (grab_widget, event);
-         if (event_widget == grab_widget)
-           GTK_PRIVATE_SET_FLAG (event_widget, GTK_LEAVE_PENDING);
-         
-         g_object_unref (event_widget);
-       }
+       gtk_widget_event (grab_widget, event);
       break;
       
     case GDK_LEAVE_NOTIFY:
-      if (GTK_WIDGET_LEAVE_PENDING (event_widget))
-       {
-         GTK_PRIVATE_UNSET_FLAG (event_widget, GTK_LEAVE_PENDING);
-         gtk_widget_event (event_widget, event);
-       }
-      else if (GTK_WIDGET_IS_SENSITIVE (grab_widget))
+      GTK_PRIVATE_UNSET_FLAG (event_widget, GTK_HAS_POINTER);
+      if (GTK_WIDGET_IS_SENSITIVE (grab_widget))
        gtk_widget_event (grab_widget, event);
       break;
       
@@ -1660,6 +1650,7 @@ typedef struct
   GtkWidget *new_grab_widget;
   gboolean   was_grabbed;
   gboolean   is_grabbed;
+  gboolean   from_grab;
 } GrabNotifyInfo;
 
 static void
@@ -1681,13 +1672,31 @@ gtk_grab_notify_foreach (GtkWidget *child,
   is_shadowed = info->new_grab_widget && !info->is_grabbed;
 
   g_object_ref (child);
+
+  if ((was_shadowed || is_shadowed) && GTK_IS_CONTAINER (child))
+    gtk_container_forall (GTK_CONTAINER (child), gtk_grab_notify_foreach, info);
   
+  if (is_shadowed)
+    {
+      GTK_PRIVATE_SET_FLAG (child, GTK_SHADOWED);
+      if (!was_shadowed && GTK_WIDGET_HAS_POINTER (child)
+         && GTK_WIDGET_IS_SENSITIVE (child))
+       _gtk_widget_synthesize_crossing (child, info->new_grab_widget,
+                                        GDK_CROSSING_GTK_GRAB);
+    }
+  else
+    {
+      GTK_PRIVATE_UNSET_FLAG (child, GTK_SHADOWED);
+      if (was_shadowed && GTK_WIDGET_HAS_POINTER (child)
+         && GTK_WIDGET_IS_SENSITIVE (child))
+       _gtk_widget_synthesize_crossing (info->old_grab_widget, child,
+                                        info->from_grab ? GDK_CROSSING_GTK_GRAB
+                                        : GDK_CROSSING_GTK_UNGRAB);
+    }
+
   if (was_shadowed != is_shadowed)
     _gtk_widget_grab_notify (child, was_shadowed);
   
-  if ((was_shadowed || is_shadowed) && GTK_IS_CONTAINER (child))
-    gtk_container_forall (GTK_CONTAINER (child), gtk_grab_notify_foreach, info);
-      
   g_object_unref (child);
   
   info->was_grabbed = was_grabbed;
@@ -1697,7 +1706,8 @@ gtk_grab_notify_foreach (GtkWidget *child,
 static void
 gtk_grab_notify (GtkWindowGroup *group,
                 GtkWidget      *old_grab_widget,
-                GtkWidget      *new_grab_widget)
+                GtkWidget      *new_grab_widget,
+                gboolean        from_grab)
 {
   GList *toplevels;
   GrabNotifyInfo info;
@@ -1707,6 +1717,7 @@ gtk_grab_notify (GtkWindowGroup *group,
 
   info.old_grab_widget = old_grab_widget;
   info.new_grab_widget = new_grab_widget;
+  info.from_grab = from_grab;
 
   g_object_ref (group);
 
@@ -1751,7 +1762,7 @@ gtk_grab_add (GtkWidget *widget)
       g_object_ref (widget);
       group->grabs = g_slist_prepend (group->grabs, widget);
 
-      gtk_grab_notify (group, old_grab_widget, widget);
+      gtk_grab_notify (group, old_grab_widget, widget, TRUE);
     }
 }
 
@@ -1787,7 +1798,7 @@ gtk_grab_remove (GtkWidget *widget)
       else
        new_grab_widget = NULL;
 
-      gtk_grab_notify (group, widget, new_grab_widget);
+      gtk_grab_notify (group, widget, new_grab_widget, FALSE);
       
       g_object_unref (widget);
     }
index c423f7b1e198147a35a692e307daf171d6caee87..5e51844c5b207e699e445f133c4589230239a49b 100644 (file)
@@ -37,7 +37,8 @@ typedef enum
 {
   PRIVATE_GTK_USER_STYLE       = 1 <<  0,
   PRIVATE_GTK_RESIZE_PENDING   = 1 <<  2,
-  PRIVATE_GTK_LEAVE_PENDING    = 1 <<  4,
+  PRIVATE_GTK_HAS_POINTER      = 1 <<  3,   /* If the pointer is above a window belonging to the widget */
+  PRIVATE_GTK_SHADOWED         = 1 <<  4,   /* If there is a grab in effect shadowing the widget */
   PRIVATE_GTK_HAS_SHAPE_MASK   = 1 <<  5,
   PRIVATE_GTK_IN_REPARENT       = 1 <<  6,
   PRIVATE_GTK_DIRECTION_SET     = 1 <<  7,   /* If the reading direction is not DIR_NONE */
@@ -54,7 +55,8 @@ typedef enum
 #define GTK_PRIVATE_FLAGS(wid)            (GTK_WIDGET (wid)->private_flags)
 #define GTK_WIDGET_USER_STYLE(obj)       ((GTK_PRIVATE_FLAGS (obj) & PRIVATE_GTK_USER_STYLE) != 0)
 #define GTK_CONTAINER_RESIZE_PENDING(obj) ((GTK_PRIVATE_FLAGS (obj) & PRIVATE_GTK_RESIZE_PENDING) != 0)
-#define GTK_WIDGET_LEAVE_PENDING(obj)    ((GTK_PRIVATE_FLAGS (obj) & PRIVATE_GTK_LEAVE_PENDING) != 0)
+#define GTK_WIDGET_HAS_POINTER(obj)      ((GTK_PRIVATE_FLAGS (obj) & PRIVATE_GTK_HAS_POINTER) != 0)
+#define GTK_WIDGET_SHADOWED(obj)         ((GTK_PRIVATE_FLAGS (obj) & PRIVATE_GTK_SHADOWED) != 0)
 #define GTK_WIDGET_HAS_SHAPE_MASK(obj)   ((GTK_PRIVATE_FLAGS (obj) & PRIVATE_GTK_HAS_SHAPE_MASK) != 0)
 #define GTK_WIDGET_IN_REPARENT(obj)      ((GTK_PRIVATE_FLAGS (obj) & PRIVATE_GTK_IN_REPARENT) != 0)
 #define GTK_WIDGET_DIRECTION_SET(obj)    ((GTK_PRIVATE_FLAGS (obj) & PRIVATE_GTK_DIRECTION_SET) != 0)
index 3d36038565c0bc68ca125af15b24df825b90bbba..6d92f0d24b1a85cae5be60b8e5ceadc24a042dc3 100644 (file)
@@ -298,6 +298,7 @@ static GQuark               quark_accel_closures = 0;
 static GQuark          quark_event_mask = 0;
 static GQuark          quark_extension_event_mode = 0;
 static GQuark          quark_parent_window = 0;
+static GQuark          quark_pointer_window = 0;
 static GQuark          quark_shape_info = 0;
 static GQuark          quark_input_shape_info = 0;
 static GQuark          quark_colormap = 0;
@@ -385,6 +386,7 @@ gtk_widget_class_init (GtkWidgetClass *klass)
   quark_event_mask = g_quark_from_static_string ("gtk-event-mask");
   quark_extension_event_mode = g_quark_from_static_string ("gtk-extension-event-mode");
   quark_parent_window = g_quark_from_static_string ("gtk-parent-window");
+  quark_pointer_window = g_quark_from_static_string ("gtk-pointer-window");
   quark_shape_info = g_quark_from_static_string ("gtk-shape-info");
   quark_input_shape_info = g_quark_from_static_string ("gtk-input-shape-info");
   quark_colormap = g_quark_from_static_string ("gtk-colormap");
@@ -8053,6 +8055,282 @@ _gtk_widget_peek_colormap (void)
   return NULL;
 }
 
+/**
+ * _gtk_widget_set_pointer_window:
+ * @widget: a #GtkWidget.
+ * @pointer_window: the new pointer window.
+ *  
+ * Sets pointer window for @widget.  Does not ref @pointer_window.
+ * Actually stores it on the #GdkScreen, but you don't need to know that.
+ **/
+void
+_gtk_widget_set_pointer_window   (GtkWidget *widget,
+                                 GdkWindow *pointer_window)
+{
+  g_return_if_fail (GTK_IS_WIDGET (widget));
+
+  GdkScreen *screen = gdk_drawable_get_screen (GDK_DRAWABLE (widget->window));
+  g_object_set_qdata (G_OBJECT (screen), quark_pointer_window, pointer_window);
+}
+
+/**
+ * _gtk_widget_get_pointer_window:
+ * @widget: a #GtkWidget.
+ *
+ * Return value: the pointer window set on the #GdkScreen @widget is attached
+ * to, or %NULL.
+ **/
+GdkWindow *
+_gtk_widget_get_pointer_window   (GtkWidget *widget)
+{
+  g_return_val_if_fail (GTK_IS_WIDGET (widget), NULL);
+
+  GdkScreen *screen = gdk_drawable_get_screen (GDK_DRAWABLE (widget->window));
+  return g_object_get_qdata (G_OBJECT (screen), quark_pointer_window);
+}
+
+static void
+synth_crossing (GtkWidget      *widget,
+               GdkEventType    type,
+               GdkWindow      *window,
+               GdkCrossingMode mode,
+               GdkNotifyType   detail)
+{
+  GdkEvent *event;
+  
+  event = gdk_event_new (type);
+
+  event->crossing.window = g_object_ref (window);
+  event->crossing.send_event = TRUE;
+  event->crossing.subwindow = g_object_ref (window);
+  event->crossing.time = GDK_CURRENT_TIME;
+  event->crossing.x = event->crossing.y = 0;
+  event->crossing.x_root = event->crossing.y_root = 0;
+  event->crossing.mode = mode;
+  event->crossing.detail = detail;
+  event->crossing.focus = FALSE;
+  event->crossing.state = 0;
+
+  if (!widget)
+    widget = gtk_get_event_widget (event);
+
+  if (widget)
+    gtk_widget_event_internal (widget, event);
+
+  gdk_event_free (event);
+}
+
+/**
+ * _gtk_widget_is_pointer_widget:
+ * @widget: a #GtkWidget
+ *
+ * Returns %TRUE if the pointer window belongs to @widget.
+ *
+ */
+gboolean
+_gtk_widget_is_pointer_widget (GtkWidget *widget)
+{
+  if (GTK_WIDGET_HAS_POINTER (widget))
+    { 
+      GdkWindow *win; 
+      GtkWidget *wid;
+
+      win = _gtk_widget_get_pointer_window (widget);
+      if (win)
+        { 
+          gdk_window_get_user_data (win, &wid);
+          if (wid == widget)
+            return TRUE;
+        }
+    }
+
+  return FALSE;
+}
+
+/**
+ * _gtk_widget_synthesize_crossing:
+ * @from: the #GtkWidget the virtual pointer is leaving.
+ * @to: the #GtkWidget the virtual pointer is moving to.
+ * @mode: the #GdkCrossingMode to place on the synthesized events.
+ *
+ * Generate crossing event(s) on widget state (sensitivity) or GTK+ grab change.
+ *
+ * The real pointer window is the window that most recently received an enter notify
+ * event.  Windows that don't select for crossing events can't become the real
+ * poiner window.  The real pointer widget that owns the real pointer window.  The
+ * effective pointer window is the same as the real pointer window unless the real
+ * pointer widget is either insensitive or there is a grab on a widget that is not
+ * an ancestor of the real pointer widget (in which case the effective pointer
+ * window should be the root window).
+ *
+ * When the effective pointer window is the same as the real poiner window, we
+ * receive crossing events from the windowing system.  When the effective pointer
+ * window changes to become different from the real pointer window we synthesize
+ * crossing events, attempting to follow X protocol rules:
+ *
+ * When the root window becomes the effective pointer window:
+ *   - leave notify on real pointer window, detail Ancestor
+ *   - leave notify on all of its ancestors, detail Virtual
+ *   - enter notify on root window, detail Inferior
+ *
+ * When the root window ceases to be the effective pointer window:
+ *   - leave notify on root window, detail Inferior
+ *   - enter notify on all ancestors of real pointer window, detail Virtual
+ *   - enter notify on real pointer window, detail Ancestor
+ */
+void
+_gtk_widget_synthesize_crossing (GtkWidget      *from,
+                                GtkWidget      *to,
+                                GdkCrossingMode mode)
+{
+  GdkWindow *from_window = NULL, *to_window = NULL;
+
+  g_return_if_fail (from != NULL || to != NULL);
+
+  if (from != NULL)
+    from_window = GTK_WIDGET_HAS_POINTER (from)
+      ? _gtk_widget_get_pointer_window (from) : from->window;
+  if (to != NULL)
+    to_window = GTK_WIDGET_HAS_POINTER (to)
+      ? _gtk_widget_get_pointer_window (to) : to->window;
+
+  if (from_window == NULL && to_window == NULL)
+    ;
+  else if (from_window != NULL && to_window == NULL)
+    {
+      GList *from_ancestors = NULL, *list;
+      GdkWindow *from_ancestor = from_window;
+
+      while (from_ancestor != NULL)
+       {
+         if (from_ancestor != NULL)
+           {
+             from_ancestor = gdk_window_get_parent (from_ancestor);
+             if (from_ancestor == NULL)
+               break;
+             from_ancestors = g_list_prepend (from_ancestors, from_ancestor);
+           }
+       }
+
+      synth_crossing (from, GDK_LEAVE_NOTIFY, from_window,
+                     mode, GDK_NOTIFY_ANCESTOR);
+      for (list = g_list_last (from_ancestors); list; list = list->prev)
+       {
+         synth_crossing (NULL, GDK_LEAVE_NOTIFY, (GdkWindow *) list->data,
+                         mode, GDK_NOTIFY_VIRTUAL);
+       }
+
+      /* XXX: enter/inferior on root window? */
+
+      g_list_free (from_ancestors);
+    }
+  else if (from_window == NULL && to_window != NULL)
+    {
+      GList *to_ancestors = NULL, *list;
+      GdkWindow *to_ancestor = to_window;
+
+      while (to_ancestor != NULL)
+       {
+         if (to_ancestor != NULL)
+           {
+             to_ancestor = gdk_window_get_parent (to_ancestor);
+             if (to_ancestor == NULL)
+               break;
+             to_ancestors = g_list_prepend (to_ancestors, to_ancestor);
+           }
+       }
+
+      /* XXX: leave/inferior on root window? */
+
+      for (list = to_ancestors; list; list = list->next)
+       {
+         synth_crossing (NULL, GDK_ENTER_NOTIFY, (GdkWindow *) list->data,
+                         mode, GDK_NOTIFY_VIRTUAL);
+       }
+      synth_crossing (to, GDK_ENTER_NOTIFY, to_window,
+                     mode, GDK_NOTIFY_ANCESTOR);
+
+      g_list_free (to_ancestors);
+    }
+  else if (from_window == to_window)
+    ;
+  else
+    {
+      GList *from_ancestors = NULL, *to_ancestors = NULL, *list;
+      GdkWindow *from_ancestor = from_window, *to_ancestor = to_window;
+
+      while (from_ancestor != NULL || to_ancestor != NULL)
+       {
+         if (from_ancestor != NULL)
+           {
+             from_ancestor = gdk_window_get_parent (from_ancestor);
+             if (from_ancestor == to_window)
+               break;
+             from_ancestors = g_list_prepend (from_ancestors, from_ancestor);
+           }
+         if (to_ancestor != NULL)
+           {
+             to_ancestor = gdk_window_get_parent (to_ancestor);
+             if (to_ancestor == from_window)
+               break;
+             to_ancestors = g_list_prepend (to_ancestors, to_ancestor);
+           }
+       }
+      if (to_ancestor == from_window)
+       {
+         if (mode != GDK_CROSSING_GTK_UNGRAB)
+           synth_crossing (from, GDK_LEAVE_NOTIFY, from_window,
+                           mode, GDK_NOTIFY_INFERIOR);
+         for (list = to_ancestors; list; list = list->next)
+           synth_crossing (NULL, GDK_ENTER_NOTIFY, (GdkWindow *) list->data, 
+                           mode, GDK_NOTIFY_VIRTUAL);
+         synth_crossing (to, GDK_ENTER_NOTIFY, to_window,
+                         mode, GDK_NOTIFY_ANCESTOR);
+       }
+      else if (from_ancestor == to_window)
+       {
+         synth_crossing (from, GDK_LEAVE_NOTIFY, from_window,
+                         mode, GDK_NOTIFY_ANCESTOR);
+         for (list = g_list_last (from_ancestors); list; list = list->prev)
+           {
+             synth_crossing (NULL, GDK_LEAVE_NOTIFY, (GdkWindow *) list->data,
+                             mode, GDK_NOTIFY_VIRTUAL);
+           }
+         if (mode != GDK_CROSSING_GTK_GRAB)
+           synth_crossing (to, GDK_ENTER_NOTIFY, to_window,
+                           mode, GDK_NOTIFY_INFERIOR);
+       }
+      else
+       {
+         while (from_ancestors != NULL && to_ancestors != NULL 
+                && from_ancestors->data == to_ancestors->data)
+           {
+             from_ancestors = g_list_delete_link (from_ancestors, 
+                                                  from_ancestors);
+             to_ancestors = g_list_delete_link (to_ancestors, to_ancestors);
+           }
+
+         synth_crossing (from, GDK_LEAVE_NOTIFY, from_window,
+                         mode, GDK_NOTIFY_NONLINEAR);
+
+         for (list = g_list_last (from_ancestors); list; list = list->prev)
+           {
+             synth_crossing (NULL, GDK_LEAVE_NOTIFY, (GdkWindow *) list->data,
+                             mode, GDK_NOTIFY_NONLINEAR_VIRTUAL);
+           }
+         for (list = to_ancestors; list; list = list->next)
+           {
+             synth_crossing (NULL, GDK_ENTER_NOTIFY, (GdkWindow *) list->data,
+                             mode, GDK_NOTIFY_NONLINEAR_VIRTUAL);
+           }
+         synth_crossing (to, GDK_ENTER_NOTIFY, to_window,
+                         mode, GDK_NOTIFY_NONLINEAR);
+       }
+      g_list_free (from_ancestors);
+      g_list_free (to_ancestors);
+    }
+}
+
 static void
 gtk_widget_propagate_state (GtkWidget           *widget,
                            GtkStateData        *data)
@@ -8108,6 +8386,16 @@ gtk_widget_propagate_state (GtkWidget           *widget,
 
       g_signal_emit (widget, widget_signals[STATE_CHANGED], 0, old_state);
 
+      if (GTK_WIDGET_HAS_POINTER (widget) && !GTK_WIDGET_SHADOWED (widget))
+       {
+         if (!GTK_WIDGET_IS_SENSITIVE (widget))
+           _gtk_widget_synthesize_crossing (widget, NULL, 
+                                            GDK_CROSSING_STATE_CHANGED);
+         else if (old_state == GTK_STATE_INSENSITIVE)
+           _gtk_widget_synthesize_crossing (NULL, widget, 
+                                            GDK_CROSSING_STATE_CHANGED);
+       }
+
       if (GTK_IS_CONTAINER (widget))
        {
          data->parent_sensitive = (GTK_WIDGET_IS_SENSITIVE (widget) != FALSE);
index dfc423e900243ab86adcb0682a335d29bead871f..a4feb4e2feea242f58b27a33362342c5840da9df 100644 (file)
@@ -833,6 +833,14 @@ void              _gtk_widget_propagate_screen_changed    (GtkWidget    *widget,
                                                           GdkScreen    *previous_screen);
 void             _gtk_widget_propagate_composited_changed (GtkWidget    *widget);
 
+void      _gtk_widget_set_pointer_window  (GtkWidget      *widget,
+                                           GdkWindow      *pointer_window);
+GdkWindow *_gtk_widget_get_pointer_window  (GtkWidget      *widget);
+gboolean   _gtk_widget_is_pointer_widget   (GtkWidget      *widget);
+void       _gtk_widget_synthesize_crossing (GtkWidget      *from,
+                                           GtkWidget      *to,
+                                           GdkCrossingMode mode);
+
 GdkColormap* _gtk_widget_peek_colormap (void);
 
 G_END_DECLS
index a565cf165f4cb6197af76383187b24de5968ccf6..905d3a7f7d06b680266297b52e6623265a04d828 100644 (file)
@@ -2052,10 +2052,6 @@ gtk_window_unset_transient_for  (GtkWindow *window)
   
   if (window->transient_parent)
     {
-      if (priv->transient_parent_group)
-       gtk_window_group_remove_window (window->group,
-                                       window);
-
       g_signal_handlers_disconnect_by_func (window->transient_parent,
                                            gtk_window_transient_parent_realized,
                                            window);
@@ -2073,7 +2069,13 @@ gtk_window_unset_transient_for  (GtkWindow *window)
         disconnect_parent_destroyed (window);
       
       window->transient_parent = NULL;
-      priv->transient_parent_group = FALSE;
+
+      if (priv->transient_parent_group)
+       {
+         priv->transient_parent_group = FALSE;
+         gtk_window_group_remove_window (window->group,
+                                         window);
+       }
     }
 }
 
index b9a5afa45afb499ccfd418eec214ad33cac95749..83629393cd665ee46e436907e72313d3f857d152 100644 (file)
@@ -51,6 +51,10 @@ TEST_PROGS                   += object
 object_SOURCES                  = object.c pixbuf-init.c
 object_LDADD                    = $(progs_ldadd)
 
+TEST_PROGS                     += crossingevents
+crossingevents_SOURCES          = crossingevents.c
+crossingevents_LDADD            = $(progs_ldadd)
+
 # this doesn't work in make distcheck, since it doesn't
 # find file-chooser-test-dir 
 # TEST_PROGS                   += filechooser
diff --git a/gtk/tests/crossingevents.c b/gtk/tests/crossingevents.c
new file mode 100644 (file)
index 0000000..7ad8d75
--- /dev/null
@@ -0,0 +1,1254 @@
+/*
+ * crossingevents.c: A test for crossing events
+ *
+ * Copyright (C) 2008 Cody Russell
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#include <gtk/gtk.h>
+#include <string.h>
+
+typedef struct {
+  GtkWidget *window;
+  GtkWidget *eventbox;
+  GtkWidget *frame;
+  GtkWidget *button;
+  GtkWidget *check;
+  gboolean   events_connected;
+  GQueue    *queue;
+} CrossingTest;
+
+typedef struct {
+  gboolean entered;
+  gchar *name;
+  gboolean synthesized;
+  GdkCrossingMode mode;
+  GdkNotifyType detail;
+} CrossingEventData;
+
+#define SLEEP_DURATION    100
+
+void start_events (CrossingTest *test);
+void stop_events (CrossingTest *test);
+
+static gboolean
+sleep_timeout_cb (gpointer data)
+{
+  gtk_main_quit ();
+  return FALSE;
+}
+
+static void
+sleep_in_main_loop (double fraction)
+{
+  /* process all pending idles and events */
+  while (g_main_context_pending (NULL))
+    g_main_context_iteration (NULL, FALSE);
+  /* sleeping probably isn't strictly necessary here */
+  gdk_threads_add_timeout_full (G_MAXINT, fraction * SLEEP_DURATION, sleep_timeout_cb, NULL, NULL);
+  gtk_main ();
+  /* process any pending idles or events that arrived during sleep */
+  while (g_main_context_pending (NULL))
+    g_main_context_iteration (NULL, FALSE);
+}
+
+void
+set_cursor (GtkWidget *widget)
+{
+  int x, y, w, h;
+
+  gdk_window_get_origin (widget->window, &x, &y);
+
+  x += widget->allocation.x;
+  y += widget->allocation.y;
+  w = widget->allocation.width;
+  h = widget->allocation.height;
+
+  gdk_display_warp_pointer (gtk_widget_get_display (widget),
+                           gtk_widget_get_screen (widget),
+                           x + w / 2,
+                           y + h / 2);
+
+  sleep_in_main_loop (0.5);
+}
+
+static gboolean
+on_enter (GtkWidget *widget, GdkEventCrossing *event, gpointer user_data)
+{
+  CrossingTest *test = (CrossingTest*)user_data;
+
+  CrossingEventData *evt = g_slice_new0 (CrossingEventData);
+  evt->entered = TRUE;
+  evt->name = g_strdup (gtk_widget_get_name (widget));
+  evt->synthesized = event->send_event;
+  evt->mode = event->mode;
+  evt->detail = event->detail;
+
+  if (!test->queue)
+    test->queue = g_queue_new ();
+
+  g_queue_push_tail (test->queue, evt);
+
+  return FALSE;
+}
+
+static gboolean
+on_leave (GtkWidget *widget, GdkEventCrossing *event, gpointer user_data)
+{
+  CrossingTest *test = (CrossingTest*)user_data;
+
+  CrossingEventData *evt = g_slice_new0 (CrossingEventData);
+  evt->entered = FALSE;
+  evt->name = g_strdup (gtk_widget_get_name (widget));
+  evt->synthesized = event->send_event;
+  evt->mode = event->mode;
+  evt->detail = event->detail;
+
+  if (!test->queue)
+    test->queue = g_queue_new ();
+
+  g_queue_push_tail (test->queue, evt);
+
+  return FALSE;
+}
+
+static void
+on_check_toggled (GtkWidget *toggle, GtkWidget *button)
+{
+  gtk_widget_set_sensitive (button, gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (toggle)));
+}
+
+static void
+sensitivity_setup (CrossingTest *test,
+                  gconstpointer user_data)
+{
+  GtkWidget *frame;
+
+  test->window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
+  gtk_widget_set_name (test->window, "W");
+  frame = gtk_frame_new ("Crossing Events");
+  test->eventbox = gtk_event_box_new ();
+  gtk_widget_set_name (test->eventbox, "E");
+
+  GtkWidget *vbox = gtk_vbox_new (FALSE, 10);
+  gtk_container_add (GTK_CONTAINER (test->window), frame);
+  gtk_container_add (GTK_CONTAINER (frame), test->eventbox);
+  gtk_container_add (GTK_CONTAINER (test->eventbox), vbox);
+
+  test->button = gtk_button_new_with_label ("Click me!");
+  gtk_widget_set_name (test->button, "B");
+  gtk_box_pack_start (GTK_BOX (vbox), test->button, FALSE, TRUE, 0);
+
+  test->check = gtk_check_button_new_with_label ("Sensitive?");
+  gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (test->check), TRUE);
+  g_signal_connect (G_OBJECT (test->check),
+                   "toggled", G_CALLBACK (on_check_toggled), test->button);
+  gtk_widget_set_name (test->check, "C");
+  gtk_box_pack_start (GTK_BOX (vbox), test->check, FALSE, TRUE, 0);
+
+  gtk_widget_show_all (test->window);
+
+  gtk_window_move (GTK_WINDOW (test->window), 0, 0);
+
+  sleep_in_main_loop (0.5);
+}
+
+static void
+sensitivity_teardown (CrossingTest *test,
+                     gconstpointer user_data)
+{
+  stop_events (test);
+  gtk_widget_destroy (test->window);
+
+  if (test->queue != NULL)
+    {
+      g_queue_clear (test->queue);
+      test->queue = NULL;
+    }
+}
+
+void
+start_events (CrossingTest *test)
+{
+  if (!test->events_connected)
+    {
+      g_object_connect (G_OBJECT (test->window),
+                       "signal::destroy", gtk_main_quit, NULL,
+                       "signal::enter-notify-event", on_enter, test,
+                       "signal::leave-notify-event", on_leave, test,
+                       NULL);
+      g_object_connect (G_OBJECT (test->eventbox),
+                       "signal::enter-notify-event", on_enter, test,
+                       "signal::leave-notify-event", on_leave, test,
+                       NULL);
+      g_object_connect (G_OBJECT (test->button),
+                       "signal::enter-notify-event", on_enter, test,
+                       "signal::leave-notify-event", on_leave, test,
+                       NULL);
+      g_object_connect (G_OBJECT (test->check),
+                       "signal::enter-notify-event", on_enter, test,
+                       "signal::leave-notify-event", on_leave, test,
+                       NULL);
+      test->events_connected = TRUE;
+    }
+
+  sleep_in_main_loop (0.5);
+}
+
+void
+stop_events (CrossingTest *test)
+{
+  if (test->events_connected)
+    {
+      g_object_disconnect (G_OBJECT (test->window),
+                          "any_signal", gtk_main_quit, NULL,
+                          "any_signal", on_enter, test,
+                          "any_signal", on_leave, test,
+                          NULL);
+      g_object_disconnect (G_OBJECT (test->eventbox),
+                          "any_signal", on_enter, test,
+                          "any_signal", on_leave, test,
+                          NULL);
+      g_object_disconnect (G_OBJECT (test->button),
+                          "any_signal", on_enter, test,
+                          "any_signal", on_leave, test,
+                          NULL);
+      g_object_disconnect (G_OBJECT (test->check),
+                          "any_signal", G_CALLBACK (on_check_toggled), test->button,
+                          "any_signal", on_enter, test,
+                          "any_signal", on_leave, test,
+                          NULL);
+      test->events_connected = FALSE;
+    }
+}
+
+void
+move_cursor_away (CrossingTest *test)
+{
+  gdk_display_warp_pointer (gtk_widget_get_display (test->window),
+                            gtk_widget_get_screen (test->window),
+                            1000, -1000);
+
+  sleep_in_main_loop (0.5);
+}
+
+void
+check_event (CrossingTest *test,
+            const gchar *name,
+            gboolean entered,
+            gboolean synthesized,
+            GdkCrossingMode mode,
+            GdkNotifyType detail)
+{
+  CrossingEventData *evt;
+
+  g_assert (test->queue != NULL);
+
+  evt = g_queue_pop_head (test->queue);
+
+  g_assert (evt->entered == entered);
+  g_assert (strcmp (evt->name, name) == 0);
+  g_assert (evt->synthesized == synthesized);
+  g_assert (evt->mode == mode);
+
+  if (evt->detail != detail)
+    g_print ("detail, evt %d vs %d\n", evt->detail, detail);
+
+  g_assert (evt->detail == detail);
+}
+
+/* Verify crossing events when moving into and out of a sensitive widget */
+static void
+cursor_on_sensitive (CrossingTest *test,
+                    gconstpointer user_data)
+{
+  move_cursor_away (test);
+
+  start_events (test);
+
+  set_cursor (test->button);
+
+  check_event (test,
+              "W",
+              TRUE,
+              FALSE,  /* native */
+              GDK_CROSSING_NORMAL,
+              GDK_NOTIFY_NONLINEAR_VIRTUAL);
+
+  check_event (test,
+              "E",
+              TRUE,
+              FALSE,  /* native */
+              GDK_CROSSING_NORMAL,
+              GDK_NOTIFY_NONLINEAR_VIRTUAL);
+
+  check_event (test,
+              "B",
+              TRUE,
+              FALSE,  /* native */
+              GDK_CROSSING_NORMAL,
+              GDK_NOTIFY_NONLINEAR);
+
+  g_assert (g_queue_is_empty (test->queue));
+
+  move_cursor_away (test);
+
+  check_event (test,
+              "B",
+              FALSE,
+              FALSE,  /* native */
+              GDK_CROSSING_NORMAL,
+              GDK_NOTIFY_NONLINEAR);
+
+  check_event (test,
+              "E",
+              FALSE,
+              FALSE,  /* native */
+              GDK_CROSSING_NORMAL,
+              GDK_NOTIFY_NONLINEAR_VIRTUAL);
+
+  check_event (test,
+              "W",
+              FALSE,
+              FALSE,  /* native */
+              GDK_CROSSING_NORMAL,
+              GDK_NOTIFY_NONLINEAR_VIRTUAL);
+
+  g_assert (g_queue_is_empty (test->queue));
+
+  stop_events (test);
+}
+
+static void
+change_sensitive_to_insensitive (CrossingTest *test,
+                                gconstpointer user_data)
+{
+  move_cursor_away (test);
+  set_cursor (test->button);
+
+  start_events (test);
+
+  gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (test->check), FALSE);
+
+  check_event (test,
+              "B",
+              FALSE,
+              TRUE,  /* synthesized */
+              GDK_CROSSING_STATE_CHANGED,
+              GDK_NOTIFY_ANCESTOR);
+
+  check_event (test,
+               "E",
+              FALSE,
+              TRUE,  /* synthesized */
+               GDK_CROSSING_STATE_CHANGED,
+               GDK_NOTIFY_VIRTUAL);
+
+  check_event (test,
+               "W",
+              FALSE,
+              TRUE,  /* synthesized */
+               GDK_CROSSING_STATE_CHANGED,
+               GDK_NOTIFY_VIRTUAL);
+
+  g_assert (g_queue_is_empty (test->queue));
+
+  stop_events (test);
+}
+
+static void
+change_insensitive_to_sensitive (CrossingTest *test,
+                                gconstpointer user_data)
+{
+  move_cursor_away (test);
+  set_cursor (test->button);
+  gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (test->check), FALSE);
+
+  start_events (test);
+
+  gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (test->check), TRUE);
+
+  check_event (test,
+               "W",
+               TRUE,
+               TRUE,  /* synthesized */
+               GDK_CROSSING_STATE_CHANGED,
+               GDK_NOTIFY_VIRTUAL);
+
+  check_event (test,
+               "E",
+               TRUE,
+              TRUE,  /* synthesized */
+               GDK_CROSSING_STATE_CHANGED,
+               GDK_NOTIFY_VIRTUAL);
+
+  check_event (test,
+               "B",
+               TRUE,
+              TRUE,  /* synthesized */
+               GDK_CROSSING_STATE_CHANGED,
+               GDK_NOTIFY_ANCESTOR);
+
+  g_assert (g_queue_is_empty (test->queue));
+
+  stop_events (test);
+}
+
+static void
+cursor_from_insensitive_to_sensitive (CrossingTest *test,
+                                     gconstpointer user_data)
+{
+  set_cursor (test->button);
+  gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (test->check), FALSE);
+
+  start_events (test);
+
+  set_cursor (test->check);
+
+  check_event (test,
+               "C",
+               TRUE,
+               FALSE,  /* native */
+               GDK_CROSSING_NORMAL,
+               GDK_NOTIFY_NONLINEAR);
+
+  g_assert (g_queue_is_empty (test->queue));
+
+  stop_events (test);
+}
+
+static void
+cursor_from_sensitive_to_insensitive (CrossingTest *test,
+                                     gconstpointer user_data)
+{
+  set_cursor (test->check);
+  gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (test->check), FALSE);
+
+  start_events (test);
+
+  set_cursor (test->button);
+
+  check_event (test,
+               "C",
+               FALSE,
+               FALSE,  /* native */
+               GDK_CROSSING_NORMAL,
+               GDK_NOTIFY_NONLINEAR);
+
+  g_assert (g_queue_is_empty (test->queue));
+
+  stop_events (test);
+}
+
+static void
+add_gtk_grab (CrossingTest *test,
+             gconstpointer user_data)
+{
+  set_cursor (test->button);
+
+  start_events (test);
+
+  gtk_grab_add (test->check);
+
+  check_event (test,
+              "B",
+              FALSE,
+              TRUE,   /* synthesized */
+              GDK_CROSSING_GTK_GRAB,
+              GDK_NOTIFY_ANCESTOR);
+
+  check_event (test,
+               "E",
+               FALSE,
+              TRUE,   /* synthesized */
+              GDK_CROSSING_GTK_GRAB,
+              GDK_NOTIFY_ANCESTOR);
+
+  check_event (test,
+               "W",
+               FALSE,
+               TRUE,   /* synthesized */
+               GDK_CROSSING_GTK_GRAB,
+              GDK_NOTIFY_ANCESTOR);
+
+  g_assert (g_queue_is_empty (test->queue));
+
+  stop_events (test);
+}
+
+static void
+remove_gtk_grab (CrossingTest *test,
+                gconstpointer user_data)
+{
+  set_cursor (test->button);
+
+  gtk_grab_add (test->check);
+
+  start_events (test);
+
+  gtk_grab_remove (test->check);
+
+  check_event (test,
+               "B",
+               TRUE,
+               TRUE,   /* synthesized */
+               GDK_CROSSING_GTK_UNGRAB,
+              GDK_NOTIFY_ANCESTOR);
+
+  check_event (test,
+               "E",
+               TRUE,
+               TRUE,   /* synthesized */
+              GDK_CROSSING_GTK_UNGRAB,
+               GDK_NOTIFY_ANCESTOR);
+
+  check_event (test,
+               "W",
+               TRUE,
+               TRUE,   /* synthesized */
+               GDK_CROSSING_GTK_UNGRAB,
+               GDK_NOTIFY_ANCESTOR);
+
+  g_assert (g_queue_is_empty (test->queue));
+
+  stop_events (test);
+}
+
+static void
+cursor_from_shadowed_to_unshadowed (CrossingTest *test,
+                                   gconstpointer user_data)
+{
+  set_cursor (test->button);
+
+  gtk_grab_add (test->check);
+
+  start_events (test);
+
+  set_cursor (test->check);
+
+  check_event (test,
+               "C",
+               FALSE,
+               FALSE,   /* native */
+               GDK_CROSSING_NORMAL,
+               GDK_NOTIFY_NONLINEAR);
+
+  check_event (test,
+               "C",
+               TRUE,
+               FALSE,   /* native */
+               GDK_CROSSING_NORMAL,
+               GDK_NOTIFY_NONLINEAR);
+
+  g_assert (g_queue_is_empty (test->queue));
+
+  stop_events (test);
+}
+
+static void
+cursor_from_unshadowed_to_shadowed (CrossingTest *test,
+                                   gconstpointer user_data)
+{
+  set_cursor (test->check);
+
+  gtk_grab_add (test->check);
+
+  start_events (test);
+
+  set_cursor (test->button);
+
+  check_event (test,
+               "C",
+               FALSE,
+               FALSE,   /* native */
+               GDK_CROSSING_NORMAL,
+               GDK_NOTIFY_NONLINEAR);
+
+  check_event (test,
+               "C",
+               TRUE,
+               FALSE,   /* native */
+               GDK_CROSSING_NORMAL,
+               GDK_NOTIFY_NONLINEAR);
+
+  g_assert (g_queue_is_empty (test->queue));
+
+  stop_events (test);
+}
+
+int
+main (int    argc,
+      char **argv)
+{
+  gtk_test_init (&argc, &argv, NULL);
+
+  g_test_add ("/crossings/cursor-on-sensitive", CrossingTest, NULL,
+             sensitivity_setup, cursor_on_sensitive, sensitivity_teardown);
+
+  g_test_add ("/crossings/change-sensitive-to-insensitive", CrossingTest, NULL,
+             sensitivity_setup, change_sensitive_to_insensitive, sensitivity_teardown);
+
+  g_test_add ("/crossings/cursor-from-insensitive-to-sensitive", CrossingTest, NULL,
+             sensitivity_setup, cursor_from_insensitive_to_sensitive, sensitivity_teardown);
+
+  g_test_add ("/crossings/cursor-from-sensitive-to-insensitive", CrossingTest, NULL,
+             sensitivity_setup, cursor_from_sensitive_to_insensitive, sensitivity_teardown);
+
+  g_test_add ("/crossings/change-insensitive-to-sensitive", CrossingTest, NULL,
+             sensitivity_setup, change_insensitive_to_sensitive, sensitivity_teardown);
+
+  g_test_add ("/crossings/add-gtk-grab", CrossingTest, NULL,
+             sensitivity_setup, add_gtk_grab, sensitivity_teardown);
+
+  g_test_add ("/crossings/remove-gtk-grab", CrossingTest, NULL,
+             sensitivity_setup, remove_gtk_grab, sensitivity_teardown);
+
+  g_test_add ("/crossings/cursor-from-shadowed-to-unshadowed", CrossingTest, NULL,
+             sensitivity_setup, cursor_from_shadowed_to_unshadowed, sensitivity_teardown);
+
+  g_test_add ("/crossings/cursor-from-unshadowed-to-shadowed", CrossingTest, NULL,
+             sensitivity_setup, cursor_from_unshadowed_to_shadowed, sensitivity_teardown);
+
+  return g_test_run ();
+}
+/*
+ * crossingevents.c: A test for crossing events
+ *
+ * Copyright (C) 2008 Cody Russell
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#include <gtk/gtk.h>
+#include <string.h>
+
+typedef struct {
+  GtkWidget *window;
+  GtkWidget *eventbox;
+  GtkWidget *frame;
+  GtkWidget *button;
+  GtkWidget *check;
+  gboolean   events_connected;
+  GQueue    *queue;
+} CrossingTest;
+
+typedef struct {
+  gboolean entered;
+  gchar *name;
+  gboolean synthesized;
+  GdkCrossingMode mode;
+  GdkNotifyType detail;
+} CrossingEventData;
+
+#define SLEEP_DURATION    100
+
+void start_events (CrossingTest *test);
+void stop_events (CrossingTest *test);
+
+static gboolean
+sleep_timeout_cb (gpointer data)
+{
+  gtk_main_quit ();
+  return FALSE;
+}
+
+static void
+sleep_in_main_loop (double fraction)
+{
+  /* process all pending idles and events */
+  while (g_main_context_pending (NULL))
+    g_main_context_iteration (NULL, FALSE);
+  /* sleeping probably isn't strictly necessary here */
+  gdk_threads_add_timeout_full (G_MAXINT, fraction * SLEEP_DURATION, sleep_timeout_cb, NULL, NULL);
+  gtk_main ();
+  /* process any pending idles or events that arrived during sleep */
+  while (g_main_context_pending (NULL))
+    g_main_context_iteration (NULL, FALSE);
+}
+
+void
+set_cursor (GtkWidget *widget)
+{
+  int x, y, w, h;
+
+  gdk_window_get_origin (widget->window, &x, &y);
+
+  x += widget->allocation.x;
+  y += widget->allocation.y;
+  w = widget->allocation.width;
+  h = widget->allocation.height;
+
+  gdk_display_warp_pointer (gtk_widget_get_display (widget),
+                           gtk_widget_get_screen (widget),
+                           x + w / 2,
+                           y + h / 2);
+
+  sleep_in_main_loop (0.5);
+}
+
+static gboolean
+on_enter (GtkWidget *widget, GdkEventCrossing *event, gpointer user_data)
+{
+  CrossingTest *test = (CrossingTest*)user_data;
+
+  CrossingEventData *evt = g_slice_new0 (CrossingEventData);
+  evt->entered = TRUE;
+  evt->name = g_strdup (gtk_widget_get_name (widget));
+  evt->synthesized = event->send_event;
+  evt->mode = event->mode;
+  evt->detail = event->detail;
+
+  if (!test->queue)
+    test->queue = g_queue_new ();
+
+  g_queue_push_tail (test->queue, evt);
+
+  return FALSE;
+}
+
+static gboolean
+on_leave (GtkWidget *widget, GdkEventCrossing *event, gpointer user_data)
+{
+  CrossingTest *test = (CrossingTest*)user_data;
+
+  CrossingEventData *evt = g_slice_new0 (CrossingEventData);
+  evt->entered = FALSE;
+  evt->name = g_strdup (gtk_widget_get_name (widget));
+  evt->synthesized = event->send_event;
+  evt->mode = event->mode;
+  evt->detail = event->detail;
+
+  if (!test->queue)
+    test->queue = g_queue_new ();
+
+  g_queue_push_tail (test->queue, evt);
+
+  return FALSE;
+}
+
+static void
+on_check_toggled (GtkWidget *toggle, GtkWidget *button)
+{
+  gtk_widget_set_sensitive (button, gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (toggle)));
+}
+
+static void
+sensitivity_setup (CrossingTest *test,
+                  gconstpointer user_data)
+{
+  GtkWidget *frame;
+
+  test->window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
+  gtk_widget_set_name (test->window, "W");
+  frame = gtk_frame_new ("Crossing Events");
+  test->eventbox = gtk_event_box_new ();
+  gtk_widget_set_name (test->eventbox, "E");
+
+  GtkWidget *vbox = gtk_vbox_new (FALSE, 10);
+  gtk_container_add (GTK_CONTAINER (test->window), frame);
+  gtk_container_add (GTK_CONTAINER (frame), test->eventbox);
+  gtk_container_add (GTK_CONTAINER (test->eventbox), vbox);
+
+  test->button = gtk_button_new_with_label ("Click me!");
+  gtk_widget_set_name (test->button, "B");
+  gtk_box_pack_start (GTK_BOX (vbox), test->button, FALSE, TRUE, 0);
+
+  test->check = gtk_check_button_new_with_label ("Sensitive?");
+  gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (test->check), TRUE);
+  g_signal_connect (G_OBJECT (test->check),
+                   "toggled", G_CALLBACK (on_check_toggled), test->button);
+  gtk_widget_set_name (test->check, "C");
+  gtk_box_pack_start (GTK_BOX (vbox), test->check, FALSE, TRUE, 0);
+
+  gtk_widget_show_all (test->window);
+
+  gtk_window_move (GTK_WINDOW (test->window), 0, 0);
+
+  sleep_in_main_loop (0.5);
+}
+
+static void
+sensitivity_teardown (CrossingTest *test,
+                     gconstpointer user_data)
+{
+  stop_events (test);
+  gtk_widget_destroy (test->window);
+
+  if (test->queue != NULL)
+    {
+      g_queue_clear (test->queue);
+      test->queue = NULL;
+    }
+}
+
+void
+start_events (CrossingTest *test)
+{
+  if (!test->events_connected)
+    {
+      g_object_connect (G_OBJECT (test->window),
+                       "signal::destroy", gtk_main_quit, NULL,
+                       "signal::enter-notify-event", on_enter, test,
+                       "signal::leave-notify-event", on_leave, test,
+                       NULL);
+      g_object_connect (G_OBJECT (test->eventbox),
+                       "signal::enter-notify-event", on_enter, test,
+                       "signal::leave-notify-event", on_leave, test,
+                       NULL);
+      g_object_connect (G_OBJECT (test->button),
+                       "signal::enter-notify-event", on_enter, test,
+                       "signal::leave-notify-event", on_leave, test,
+                       NULL);
+      g_object_connect (G_OBJECT (test->check),
+                       "signal::enter-notify-event", on_enter, test,
+                       "signal::leave-notify-event", on_leave, test,
+                       NULL);
+      test->events_connected = TRUE;
+    }
+
+  sleep_in_main_loop (0.5);
+}
+
+void
+stop_events (CrossingTest *test)
+{
+  if (test->events_connected)
+    {
+      g_object_disconnect (G_OBJECT (test->window),
+                          "any_signal", gtk_main_quit, NULL,
+                          "any_signal", on_enter, test,
+                          "any_signal", on_leave, test,
+                          NULL);
+      g_object_disconnect (G_OBJECT (test->eventbox),
+                          "any_signal", on_enter, test,
+                          "any_signal", on_leave, test,
+                          NULL);
+      g_object_disconnect (G_OBJECT (test->button),
+                          "any_signal", on_enter, test,
+                          "any_signal", on_leave, test,
+                          NULL);
+      g_object_disconnect (G_OBJECT (test->check),
+                          "any_signal", G_CALLBACK (on_check_toggled), test->button,
+                          "any_signal", on_enter, test,
+                          "any_signal", on_leave, test,
+                          NULL);
+      test->events_connected = FALSE;
+    }
+}
+
+void
+move_cursor_away (CrossingTest *test)
+{
+  gdk_display_warp_pointer (gtk_widget_get_display (test->window),
+                            gtk_widget_get_screen (test->window),
+                            1000, -1000);
+
+  sleep_in_main_loop (0.5);
+}
+
+void
+check_event (CrossingTest *test,
+            const gchar *name,
+            gboolean entered,
+            gboolean synthesized,
+            GdkCrossingMode mode,
+            GdkNotifyType detail)
+{
+  CrossingEventData *evt;
+
+  g_assert (test->queue != NULL);
+
+  evt = g_queue_pop_head (test->queue);
+
+  g_assert (evt->entered == entered);
+  g_assert (strcmp (evt->name, name) == 0);
+  g_assert (evt->synthesized == synthesized);
+  g_assert (evt->mode == mode);
+
+  if (evt->detail != detail)
+    g_print ("detail, evt %d vs %d\n", evt->detail, detail);
+
+  g_assert (evt->detail == detail);
+}
+
+/* Verify crossing events when moving into and out of a sensitive widget */
+static void
+cursor_on_sensitive (CrossingTest *test,
+                    gconstpointer user_data)
+{
+  move_cursor_away (test);
+
+  start_events (test);
+
+  set_cursor (test->button);
+
+  check_event (test,
+              "W",
+              TRUE,
+              FALSE,  /* native */
+              GDK_CROSSING_NORMAL,
+              GDK_NOTIFY_NONLINEAR_VIRTUAL);
+
+  check_event (test,
+              "E",
+              TRUE,
+              FALSE,  /* native */
+              GDK_CROSSING_NORMAL,
+              GDK_NOTIFY_NONLINEAR_VIRTUAL);
+
+  check_event (test,
+              "B",
+              TRUE,
+              FALSE,  /* native */
+              GDK_CROSSING_NORMAL,
+              GDK_NOTIFY_NONLINEAR);
+
+  g_assert (g_queue_is_empty (test->queue));
+
+  move_cursor_away (test);
+
+  check_event (test,
+              "B",
+              FALSE,
+              FALSE,  /* native */
+              GDK_CROSSING_NORMAL,
+              GDK_NOTIFY_NONLINEAR);
+
+  check_event (test,
+              "E",
+              FALSE,
+              FALSE,  /* native */
+              GDK_CROSSING_NORMAL,
+              GDK_NOTIFY_NONLINEAR_VIRTUAL);
+
+  check_event (test,
+              "W",
+              FALSE,
+              FALSE,  /* native */
+              GDK_CROSSING_NORMAL,
+              GDK_NOTIFY_NONLINEAR_VIRTUAL);
+
+  g_assert (g_queue_is_empty (test->queue));
+
+  stop_events (test);
+}
+
+static void
+change_sensitive_to_insensitive (CrossingTest *test,
+                                gconstpointer user_data)
+{
+  move_cursor_away (test);
+  set_cursor (test->button);
+
+  start_events (test);
+
+  gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (test->check), FALSE);
+
+  check_event (test,
+              "B",
+              FALSE,
+              TRUE,  /* synthesized */
+              GDK_CROSSING_STATE_CHANGED,
+              GDK_NOTIFY_ANCESTOR);
+
+  check_event (test,
+               "E",
+              FALSE,
+              TRUE,  /* synthesized */
+               GDK_CROSSING_STATE_CHANGED,
+               GDK_NOTIFY_VIRTUAL);
+
+  check_event (test,
+               "W",
+              FALSE,
+              TRUE,  /* synthesized */
+               GDK_CROSSING_STATE_CHANGED,
+               GDK_NOTIFY_VIRTUAL);
+
+  g_assert (g_queue_is_empty (test->queue));
+
+  stop_events (test);
+}
+
+static void
+change_insensitive_to_sensitive (CrossingTest *test,
+                                gconstpointer user_data)
+{
+  move_cursor_away (test);
+  set_cursor (test->button);
+  gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (test->check), FALSE);
+
+  start_events (test);
+
+  gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (test->check), TRUE);
+
+  check_event (test,
+               "W",
+               TRUE,
+               TRUE,  /* synthesized */
+               GDK_CROSSING_STATE_CHANGED,
+               GDK_NOTIFY_VIRTUAL);
+
+  check_event (test,
+               "E",
+               TRUE,
+              TRUE,  /* synthesized */
+               GDK_CROSSING_STATE_CHANGED,
+               GDK_NOTIFY_VIRTUAL);
+
+  check_event (test,
+               "B",
+               TRUE,
+              TRUE,  /* synthesized */
+               GDK_CROSSING_STATE_CHANGED,
+               GDK_NOTIFY_ANCESTOR);
+
+  g_assert (g_queue_is_empty (test->queue));
+
+  stop_events (test);
+}
+
+static void
+cursor_from_insensitive_to_sensitive (CrossingTest *test,
+                                     gconstpointer user_data)
+{
+  set_cursor (test->button);
+  gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (test->check), FALSE);
+
+  start_events (test);
+
+  set_cursor (test->check);
+
+  check_event (test,
+               "C",
+               TRUE,
+               FALSE,  /* native */
+               GDK_CROSSING_NORMAL,
+               GDK_NOTIFY_NONLINEAR);
+
+  g_assert (g_queue_is_empty (test->queue));
+
+  stop_events (test);
+}
+
+static void
+cursor_from_sensitive_to_insensitive (CrossingTest *test,
+                                     gconstpointer user_data)
+{
+  set_cursor (test->check);
+  gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (test->check), FALSE);
+
+  start_events (test);
+
+  set_cursor (test->button);
+
+  check_event (test,
+               "C",
+               FALSE,
+               FALSE,  /* native */
+               GDK_CROSSING_NORMAL,
+               GDK_NOTIFY_NONLINEAR);
+
+  g_assert (g_queue_is_empty (test->queue));
+
+  stop_events (test);
+}
+
+static void
+add_gtk_grab (CrossingTest *test,
+             gconstpointer user_data)
+{
+  set_cursor (test->button);
+
+  start_events (test);
+
+  gtk_grab_add (test->check);
+
+  check_event (test,
+              "B",
+              FALSE,
+              TRUE,   /* synthesized */
+              GDK_CROSSING_GTK_GRAB,
+              GDK_NOTIFY_ANCESTOR);
+
+  check_event (test,
+               "E",
+               FALSE,
+              TRUE,   /* synthesized */
+              GDK_CROSSING_GTK_GRAB,
+              GDK_NOTIFY_ANCESTOR);
+
+  check_event (test,
+               "W",
+               FALSE,
+               TRUE,   /* synthesized */
+               GDK_CROSSING_GTK_GRAB,
+              GDK_NOTIFY_ANCESTOR);
+
+  g_assert (g_queue_is_empty (test->queue));
+
+  stop_events (test);
+}
+
+static void
+remove_gtk_grab (CrossingTest *test,
+                gconstpointer user_data)
+{
+  set_cursor (test->button);
+
+  gtk_grab_add (test->check);
+
+  start_events (test);
+
+  gtk_grab_remove (test->check);
+
+  check_event (test,
+               "B",
+               TRUE,
+               TRUE,   /* synthesized */
+               GDK_CROSSING_GTK_UNGRAB,
+              GDK_NOTIFY_ANCESTOR);
+
+  check_event (test,
+               "E",
+               TRUE,
+               TRUE,   /* synthesized */
+              GDK_CROSSING_GTK_UNGRAB,
+               GDK_NOTIFY_ANCESTOR);
+
+  check_event (test,
+               "W",
+               TRUE,
+               TRUE,   /* synthesized */
+               GDK_CROSSING_GTK_UNGRAB,
+               GDK_NOTIFY_ANCESTOR);
+
+  g_assert (g_queue_is_empty (test->queue));
+
+  stop_events (test);
+}
+
+static void
+cursor_from_shadowed_to_unshadowed (CrossingTest *test,
+                                   gconstpointer user_data)
+{
+  set_cursor (test->button);
+
+  gtk_grab_add (test->check);
+
+  start_events (test);
+
+  set_cursor (test->check);
+
+  check_event (test,
+               "C",
+               FALSE,
+               FALSE,   /* native */
+               GDK_CROSSING_NORMAL,
+               GDK_NOTIFY_NONLINEAR);
+
+  check_event (test,
+               "C",
+               TRUE,
+               FALSE,   /* native */
+               GDK_CROSSING_NORMAL,
+               GDK_NOTIFY_NONLINEAR);
+
+  g_assert (g_queue_is_empty (test->queue));
+
+  stop_events (test);
+}
+
+static void
+cursor_from_unshadowed_to_shadowed (CrossingTest *test,
+                                   gconstpointer user_data)
+{
+  set_cursor (test->check);
+
+  gtk_grab_add (test->check);
+
+  start_events (test);
+
+  set_cursor (test->button);
+
+  check_event (test,
+               "C",
+               FALSE,
+               FALSE,   /* native */
+               GDK_CROSSING_NORMAL,
+               GDK_NOTIFY_NONLINEAR);
+
+  check_event (test,
+               "C",
+               TRUE,
+               FALSE,   /* native */
+               GDK_CROSSING_NORMAL,
+               GDK_NOTIFY_NONLINEAR);
+
+  g_assert (g_queue_is_empty (test->queue));
+
+  stop_events (test);
+}
+
+int
+main (int    argc,
+      char **argv)
+{
+  gtk_test_init (&argc, &argv, NULL);
+
+  g_test_add ("/crossings/cursor-on-sensitive", CrossingTest, NULL,
+             sensitivity_setup, cursor_on_sensitive, sensitivity_teardown);
+
+  g_test_add ("/crossings/change-sensitive-to-insensitive", CrossingTest, NULL,
+             sensitivity_setup, change_sensitive_to_insensitive, sensitivity_teardown);
+
+  g_test_add ("/crossings/cursor-from-insensitive-to-sensitive", CrossingTest, NULL,
+             sensitivity_setup, cursor_from_insensitive_to_sensitive, sensitivity_teardown);
+
+  g_test_add ("/crossings/cursor-from-sensitive-to-insensitive", CrossingTest, NULL,
+             sensitivity_setup, cursor_from_sensitive_to_insensitive, sensitivity_teardown);
+
+  g_test_add ("/crossings/change-insensitive-to-sensitive", CrossingTest, NULL,
+             sensitivity_setup, change_insensitive_to_sensitive, sensitivity_teardown);
+
+  g_test_add ("/crossings/add-gtk-grab", CrossingTest, NULL,
+             sensitivity_setup, add_gtk_grab, sensitivity_teardown);
+
+  g_test_add ("/crossings/remove-gtk-grab", CrossingTest, NULL,
+             sensitivity_setup, remove_gtk_grab, sensitivity_teardown);
+
+  g_test_add ("/crossings/cursor-from-shadowed-to-unshadowed", CrossingTest, NULL,
+             sensitivity_setup, cursor_from_shadowed_to_unshadowed, sensitivity_teardown);
+
+  g_test_add ("/crossings/cursor-from-unshadowed-to-shadowed", CrossingTest, NULL,
+             sensitivity_setup, cursor_from_unshadowed_to_shadowed, sensitivity_teardown);
+
+  return g_test_run ();
+}